Skip to content

Scaffold ObjectStack Mobile runtime with Expo Router, NativeWind v4, and shadcn-style UI#1

Merged
hotlong merged 2 commits intomainfrom
copilot/create-objectstack-mobile-runtime
Feb 8, 2026
Merged

Scaffold ObjectStack Mobile runtime with Expo Router, NativeWind v4, and shadcn-style UI#1
hotlong merged 2 commits intomainfrom
copilot/create-objectstack-mobile-runtime

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 7, 2026

Enterprise low-code mobile runtime scaffold — an app container that dynamically renders UI from JSON metadata, similar to Salesforce Mobile or Workday.

Stack

  • Expo SDK 54, managed workflow, TypeScript strict
  • Expo Router file-based navigation with bottom tabs (Home, Apps, Notifications, Profile)
  • NativeWind v4 + Tailwind CSS v3, CSS-variable design tokens with light/dark mode
  • zustand + @tanstack/react-query wired in root layout
  • lucide-react-native icons, expo-haptics for tactile feedback

Design System

CSS-variable token system in global.css with semantic Tailwind mappings (primary, card, muted, destructive, etc.). Enterprise palette: slate/gray base, deep blue primary (#1e40af light / #60a5fb dark).

UI Components (components/ui/)

shadcn/ui pattern — cn() utility via clsx + tailwind-merge, className props, zero StyleSheet.create:

  • Button — variants: default | destructive | outline | ghost, sizes: sm | default | lg, haptic on press
  • Input — focus ring state, forwarded ref
  • Card — composable: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter

Metadata-Driven Rendering

Home screen demonstrates the core pattern — dynamic UI from JSON metadata:

const dashboardMetadata: DashboardCardMeta[] = [
  { type: "card", title: "Monthly Sales", value: "$120,000", trend: "+12%", icon: "dollar-sign" },
  { type: "card", title: "Active Users", value: "8,420", trend: "+5.2%", icon: "users" },
];

function renderFromMetadata(metadata: DashboardCardMeta[]) {
  return metadata.map((item, index) => {
    switch (item.type) {
      case "card":
        return <MetadataCardRenderer key={index} meta={item} />;
      default:
        return null;
    }
  });
}

File Structure

app/
  _layout.tsx              # Root: QueryClientProvider + SafeAreaProvider
  (tabs)/_layout.tsx       # Tab bar config
  (tabs)/index.tsx         # Dashboard with metadata renderer
  (tabs)/apps|notifications|profile.tsx
components/ui/             # Owned UI primitives
lib/utils.ts               # cn()
global.css                 # Design tokens (light + dark)
tailwind.config.js         # Semantic color mapping
metro.config.js            # NativeWind integration

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.expo.dev
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/mobile/mobile/node_modules/.bin/expo export --platform web --output-dir /tmp/expo-build modules/@npmcli/run-script/lib/node-gyp-bin/node (dns block)
  • cdp.expo.dev
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/.npm/_npx/956a30255ff895e3/node_modules/.bin/create-expo-app . --template blank-typescript --no-install (dns block)
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/mobile/mobile/node_modules/.bin/expo export --platform web --output-dir /tmp/expo-build modules/@npmcli/run-script/lib/node-gyp-bin/node (dns block)
    • Triggering command: /opt/hostedtoolcache/node/24.13.0/x64/bin/node /opt/hostedtoolcache/node/24.13.0/x64/bin/node /home/REDACTED/work/mobile/mobile/node_modules/expo/node_modules/@expo/cli/build/src/utils/telemetry/clients/flushFetchDetached.js /tmp/2458ece080fef03ae2cb36950afd3281/expo-telemetry.json (dns block)

If you need me to access, download, or install something from one of these locations, you can either:


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…omponents

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Create initial structure for ObjectStack Mobile platform Scaffold ObjectStack Mobile runtime with Expo Router, NativeWind v4, and shadcn-style UI Feb 7, 2026
Copilot AI requested a review from hotlong February 7, 2026 18:52
@hotlong hotlong marked this pull request as ready for review February 8, 2026 01:07
Copilot AI review requested due to automatic review settings February 8, 2026 01:07
@hotlong hotlong merged commit 056320f into main Feb 8, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Scaffolds the ObjectStack Mobile runtime baseline using Expo Router navigation, NativeWind/Tailwind styling with CSS-variable tokens, and a small shadcn-style UI primitive set to support metadata-driven UI rendering.

Changes:

  • Adds Expo Router app structure with root providers (SafeArea + React Query) and a 4-tab navigation layout.
  • Introduces token-based theming (global.css + tailwind.config.js) and NativeWind/Metro/Babel wiring.
  • Adds initial UI primitives (Button, Card, Input) plus a dashboard screen that renders cards from JSON-like metadata.

Reviewed changes

Copilot reviewed 16 out of 25 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tsconfig.json Enables strict TS and ~/ path alias; includes NativeWind + Expo env type files.
tailwind.config.js Sets up NativeWind preset and semantic colors backed by CSS variables.
package.json Adds Expo/Router/NativeWind/Query/Zustand + supporting deps and scripts.
nativewind-env.d.ts Adds NativeWind type references.
metro.config.js Wires NativeWind Metro integration with global.css as input.
lib/utils.ts Adds cn() helper (clsx + tailwind-merge).
global.css Defines light/dark design tokens as CSS variables.
components/ui/Input.tsx Adds token-styled TextInput with focus ring and className support.
components/ui/Card.tsx Adds composable Card primitives (header/title/content/etc.).
components/ui/Button.tsx Adds Pressable-based button variants/sizes with haptics and className support.
babel.config.js Configures Expo Babel preset + NativeWind Babel integration.
assets/splash-icon.png Adds splash image asset.
assets/icon.png Adds app icon asset.
assets/favicon.png Adds web favicon asset.
assets/adaptive-icon.png Adds Android adaptive icon asset.
app/_layout.tsx Root layout: imports global CSS and sets up QueryClientProvider + SafeAreaProvider + Stack.
app/(tabs)/profile.tsx Adds Profile tab placeholder UI using Button primitives.
app/(tabs)/notifications.tsx Adds Notifications tab placeholder UI.
app/(tabs)/index.tsx Adds Dashboard tab rendering cards from metadata (JSON-like).
app/(tabs)/apps.tsx Adds Apps tab placeholder UI.
app/(tabs)/_layout.tsx Configures bottom tabs and header/tab bar appearance.
app.json Sets Expo app metadata, icons/splash, new arch flag, and expo-router plugin.
README.md Documents stack, structure, and basic getting-started instructions.
.gitignore Adds standard Node/Expo/native ignore patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread components/ui/Button.tsx
Comment on lines +37 to +44
export interface ButtonProps extends PressableProps {
variant?: keyof typeof buttonVariants;
size?: keyof typeof buttonSizes;
className?: string;
textClassName?: string;
children: React.ReactNode;
style?: ViewStyle;
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ButtonProps re-declares style?: ViewStyle, which narrows/incompatibly overrides PressableProps['style'] (which supports StyleProp<ViewStyle> and functional styles). This can cause TS errors for callers passing style arrays or a style callback. Remove the custom style field or type it as PressableProps['style'] / StyleProp<ViewStyle> to match React Native’s API.

Copilot uses AI. Check for mistakes.
Comment thread components/ui/Input.tsx
"h-12 text-base text-foreground placeholder:text-muted-foreground",
className
)}
placeholderTextColor="#94a3b8"
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeholderTextColor is hard-coded to #94a3b8, which bypasses your token system and also conflicts with the placeholder:text-muted-foreground class. This will make the placeholder color incorrect in light mode (and harder to theme). Prefer deriving the placeholder color from tokens / theme (or remove the hard-coded prop and rely on one approach consistently).

Suggested change
placeholderTextColor="#94a3b8"

Copilot uses AI. Check for mistakes.
Comment thread app/(tabs)/_layout.tsx
Comment on lines +14 to +22
headerStyle: { backgroundColor: "#ffffff" },
headerTitleStyle: { fontWeight: "700", fontSize: 17 },
headerShadowVisible: false,
tabBarActiveTintColor: "#1e40af",
tabBarInactiveTintColor: "#94a3b8",
tabBarStyle: {
borderTopColor: "#e2e8f0",
backgroundColor: "#ffffff",
},
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tab/header colors are hard-coded hex values (#ffffff, #1e40af, #94a3b8, #e2e8f0). This bypasses the CSS-variable design tokens and will look wrong once dark mode is enabled. Consider sourcing these from the theme (e.g., via token-backed colors or a color-scheme switch) so the navigation chrome stays in sync with global.css tokens.

Copilot uses AI. Check for mistakes.
Comment thread app/(tabs)/index.tsx
Comment on lines +75 to +78
return metadata.map((item, index) => {
switch (item.type) {
case "card":
return <MetadataCardRenderer key={index} meta={item} />;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderFromMetadata uses key={index}. If this metadata becomes dynamic (insert/reorder/remove), index keys can lead to incorrect component reuse and UI state bugs. Prefer a stable key from the metadata (e.g., add an id field, or use a unique title if guaranteed unique).

Suggested change
return metadata.map((item, index) => {
switch (item.type) {
case "card":
return <MetadataCardRenderer key={index} meta={item} />;
return metadata.map((item) => {
switch (item.type) {
case "card":
return <MetadataCardRenderer key={item.title} meta={item} />;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants